"""Entity definitions for the simplified Minecraft clone."""

from __future__ import annotations

import random
from dataclasses import dataclass

import pygame

from config import ENEMY_CONFIG, PLAYER_CONFIG
from world import World


def _frame_scalar(delta_seconds: float) -> float:
    return delta_seconds * 60.0


class Player:
    def __init__(self, start_x: int, start_y: int) -> None:
        self.rect = pygame.Rect(start_x, start_y, PLAYER_CONFIG.width, PLAYER_CONFIG.height)
        self.velocity_y: float = 0.0
        self.on_ground = False
        self.invulnerability_timer = 0.0

    def handle_input(self, keys: pygame.key.ScancodeWrapper, delta_seconds: float, world: World) -> None:
        scalar = _frame_scalar(delta_seconds)
        move_distance = PLAYER_CONFIG.speed * scalar
        dx = 0.0
        if keys[pygame.K_LEFT]:
            dx -= move_distance
        if keys[pygame.K_RIGHT]:
            dx += move_distance
        if dx:
            self._move_horizontal(dx, world)

    def try_jump(self, game_state) -> bool:  # type: ignore[no-untyped-def]
        if not self.on_ground:
            return False
        if not game_state.consume_stamina(PLAYER_CONFIG.stamina_jump_cost):
            return False
        self.velocity_y = -PLAYER_CONFIG.jump_velocity
        self.on_ground = False
        return True

    def update(self, delta_seconds: float, world: World, game_state) -> None:  # type: ignore[no-untyped-def]
        if self.invulnerability_timer > 0:
            self.invulnerability_timer = max(0.0, self.invulnerability_timer - delta_seconds)

        scalar = _frame_scalar(delta_seconds)
        gravity_force = PLAYER_CONFIG.gravity * scalar
        self.velocity_y += gravity_force
        self.velocity_y = min(self.velocity_y, PLAYER_CONFIG.jump_velocity)
        self._move_vertical(self.velocity_y * scalar, world)
        self._clamp_to_world(world)

        if self.on_ground:
            game_state.recover_stamina()

    def _move_horizontal(self, amount: float, world: World) -> None:
        self.rect.x += int(amount)
        for block in world.get_colliding_blocks(self.rect):
            block_rect = world.get_block_rect(block)
            if amount > 0:
                self.rect.right = block_rect.left
            else:
                self.rect.left = block_rect.right

    def _move_vertical(self, amount: float, world: World) -> None:
        self.rect.y += int(amount)
        self.on_ground = False
        for block in world.get_colliding_blocks(self.rect):
            block_rect = world.get_block_rect(block)
            if amount > 0:
                self.rect.bottom = block_rect.top
                self.on_ground = True
                self.velocity_y = 0
            else:
                self.rect.top = block_rect.bottom
                self.velocity_y = 0

    def _clamp_to_world(self, world: World) -> None:
        bounds = world.world_bounds()
        if self.rect.left < bounds.left:
            self.rect.left = bounds.left
        if self.rect.right > bounds.right:
            self.rect.right = bounds.right
        if self.rect.top < 0:
            self.rect.top = 0
            self.velocity_y = 0
        max_bottom = world.ground_height - PLAYER_CONFIG.height
        if self.rect.bottom > world.screen_height:
            self.rect.bottom = world.screen_height
            self.velocity_y = 0
            self.on_ground = True

    def take_damage(self, amount: int, game_state) -> None:  # type: ignore[no-untyped-def]
        if self.invulnerability_timer > 0:
            return
        game_state.change_health(-amount)
        self.invulnerability_timer = 1.0

    def draw(self, screen: pygame.Surface) -> None:
        color = (255, 0, 0) if self.invulnerability_timer <= 0 else (255, 128, 128)
        pygame.draw.rect(screen, color, self.rect)


@dataclass(slots=True)
class Enemy:
    rect: pygame.Rect
    direction: int
    base_y: int

    @classmethod
    def create(cls, x: int, y: int) -> "Enemy":
        rect = pygame.Rect(x, y, ENEMY_CONFIG.width, ENEMY_CONFIG.height)
        direction = random.choice([-1, 1])
        return cls(rect=rect, direction=direction, base_y=y)

    def update(self, world: World, game_state, delta_seconds: float) -> None:  # type: ignore[no-untyped-def]
        scalar = _frame_scalar(delta_seconds)
        speed_multiplier = {
            "day": ENEMY_CONFIG.day_speed_multiplier,
            "dusk": 1.0,
            "night": ENEMY_CONFIG.night_speed_multiplier,
        }.get(game_state.day_phase, 1.0)
        move_distance = ENEMY_CONFIG.base_speed * speed_multiplier * scalar
        self.rect.x += int(move_distance * self.direction)

        bounds = world.world_bounds()
        if self.rect.left <= bounds.left or self.rect.right >= bounds.right:
            self.direction *= -1

        collided = False
        for block in world.get_colliding_blocks(self.rect):
            block_rect = world.get_block_rect(block)
            collided = True
            if self.direction > 0:
                self.rect.right = block_rect.left
            else:
                self.rect.left = block_rect.right
        if collided:
            self.direction *= -1

        # gravity
        self.rect.y += int(PLAYER_CONFIG.gravity * scalar)
        grounded = False
        for block in world.get_colliding_blocks(self.rect):
            block_rect = world.get_block_rect(block)
            self.rect.bottom = block_rect.top
            grounded = True
        if not grounded and self.rect.bottom > world.screen_height:
            self.rect.bottom = world.screen_height

    def draw(self, screen: pygame.Surface) -> None:
        pygame.draw.rect(screen, (255, 255, 0), self.rect)
